// DeflaterWrite.cs
//
// Copyright (C) 2010 Thomas Maierhofer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
// 
// As a special exception, the copyright holders of this library give you
// permission to link this library with independent modules to produce an
// executable, regardless of the license terms of these independent
// modules, and to copy and distribute the resulting executable under
// terms of your choice, provided that you also meet, for each linked
// independent module, the terms and conditions of the license of that
// module.  An independent module is a module which is not derived from
// or based on this library.  If you modify this library, you may extend
// this exception to your version of the library, but you are not
// obligated to do so.  If you do not wish to do so, delete this
// exception statement from your version.


namespace WAF.Content
{
    using System;
    using System.Text;
    public partial class Deflater
    {

        /// <summary>
        /// Writes and compresses the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="length">The length.</param>
        /// <param name="encoding">The encoding used to encode the text.</param>
        /// <remarks>Currently following encodings write directly into the internal buffer: UTF-8</remarks>
        public void Write(string text, int offset, int length, Encoding encoding)
        {
            if (encoding == Encoding.UTF32)
            {
                while (length > 0)
                {
                    if (windowStart >= WSIZE + MAX_DIST) 
                        SlideWindow();

                    int endOffset = 2 * WSIZE - windowLookahead - windowStart;
                    endOffset /= 4;  // UTF-8 uses max 4 bytes per char
                    if (endOffset > length) endOffset = length;
                    
                    int startOffset = offset;
                    endOffset += offset;

                    int windowStartIndex = windowStart + windowLookahead;
                    int windowIndex = windowStartIndex;

                    while (offset < endOffset)
                    {
                        ulong utf32 = text[offset++];

                        if (utf32 < 2048)
                        {
                            if (utf32 < 128)
                            {   // 1 Byte: 0-127
                                this.window[windowIndex++] = (byte) utf32;
                            }
                            else
                            {   // 2 Byte: 128-2047
                                this.window[windowIndex++] = (byte)((utf32 >> 6) + 0xC0);
                                this.window[windowIndex++] = (byte)((utf32 & 0x3F) + 0x80);
                            }
                        }
                        else
                        {
                            ulong surrogateMask = utf32 & 0xFC00;
                            switch (surrogateMask)
                            {
                                case 0xD800: // High Surrogate
                                    utf32 &= 0x03FFu;
                                    utf32 <<= 10;
                                    // TODO: Check the Surrogate
                                    utf32 += (text[offset++] & 0x03FFu); // Low Surrogate
                                    utf32 += 0x10000u;
                                    break;

                                case 0xDC00: // Low Surrogate
                                    utf32 &= 0x03FF;
                                    // TODO: Check the Surrogate
                                    utf32 += (text[offset++] & 0x03FFu) << 10; // High Surrogate
                                    utf32 += 0x10000u;
                                    break;
                            }

                            if (utf32 < 65536)
                            {   // 3 Byte 2048-65535
                                this.window[windowIndex++] = (byte)((utf32 >> 12) + 0xC0);
                                this.window[windowIndex++] = (byte)(((utf32 >> 6) & 0x3F) + 0x80);
                                this.window[windowIndex++] = (byte)((utf32 & 0x3F) + 0x80);
                            }
                            else
                            {   // 4 Bytes 65536-2097152
                                this.window[windowIndex++] = (byte)((utf32 >> 18) + 0xC0);
                                this.window[windowIndex++] = (byte)(((utf32 >> 12) & 0x3F) + 0x80);
                                this.window[windowIndex++] = (byte)(((utf32 >> 6) & 0x3F) + 0x80);
                                this.window[windowIndex++] = (byte)((utf32 & 0x3F) + 0x80);
                            }
                        }
                    
                    }
                    length -= offset - startOffset;
                    int copiedBytes = windowIndex - windowStartIndex;
                    this.windowLookahead += copiedBytes;
                    Compress(copiedBytes);
                }
            }
            else
            {
                byte[] buffer = new byte[encoding.GetMaxByteCount(length)];
                int count = encoding.GetBytes(text, offset, length, buffer, 0);
                Write(buffer, 0, count);
            }

        }


        public void Write(byte[] data, int offset, int length)
        {
            while (length > 0)
            {
                if (windowStart >= WSIZE + MAX_DIST) 
                    SlideWindow();

                int bytesToCopy = 2 * WSIZE - windowLookahead - windowStart;
                if (bytesToCopy > length) bytesToCopy = length;

                Buffer.BlockCopy(data, offset, window, windowStart + windowLookahead, bytesToCopy);
                length -= bytesToCopy;
                offset += bytesToCopy;
                this.windowLookahead += bytesToCopy;

                Compress(bytesToCopy);
            }

        }


        public void FinishWriting()
        {
            if (this.fastCompression) while (DeflateOptimizedSpeed(true)) { }
            else while (DeflateOptimizedSize(true)) { };



            OutputBufferAlignToByte();

            switch (this.dataFormat)
            {
                case DeflaterConfig.StreamFormat.GZIP:
                    // Write a GZIP (RFC 1952) Footer
                    crc32 ^= 0xFFFFFFFF;
                    OutputBufferWriteShortLSBFirst((uint)crc32 & 0xffff);
                    OutputBufferWriteShortLSBFirst((uint)crc32 >> 16);
                    OutputBufferWriteShortLSBFirst((uint)originalSize & 0xffff);
                    OutputBufferWriteShortLSBFirst((uint)originalSize >> 16);
                    break;

                case DeflaterConfig.StreamFormat.ZLIB:

                    // Write a ZLIB (RFC 1950) Footer
                    checksumAdler32S1 %= 65521;
                    checksumAdler32S2 %= 65521;
                    OutputBufferWriteShort((uint)checksumAdler32S2 & 0xffff);
                    OutputBufferWriteShort((uint)checksumAdler32S1 & 0xffff);
                    break;
            }

            OutputBufferFlush();

        }
    }
}
